package jalgebrava.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * Unary functions.
 * @param <A>
 * @param <B>
 */
public abstract class F<A, B> {
	public abstract B apply(A a);

	// some static helpers
	public static <A,B,T> F<A,B> method(Class<T> sourceClass, String methodName, Class<A> argumentClass, Class<B> resultClass) {
		try {
			final Method m = sourceClass.getDeclaredMethod(methodName, argumentClass);
			if (!m.getReturnType().equals(resultClass)) {				
				throw new NoSuchMethodException();
			}

			return new F<A, B>() {
	
				@SuppressWarnings("unchecked")
				@Override
				public B apply(A a) {
					try {
						return (B) m.invoke(null, a);
					} catch (NullPointerException npe) {
						throw new RuntimeException("not a static method", npe);
					} catch (InvocationTargetException ite) {
						throw new RuntimeException(ite);
					} catch (IllegalAccessException iae) {
						throw new RuntimeException(iae);
					}
				}
				
			};
		} catch (NoSuchMethodException nsme) {
			throw new RuntimeException(nsme);
		} 
	}
	
	public static <A,B> F<A,B> fromMap(final Map<A, B> map) {
		return new F<A, B>() {

			@Override
			public B apply(A a) {
				return map.get(a);
			}
			
		};
	}
	public static <A,B> F<A,B> cached(final F<A,B> f) {
		return new F<A,B>() {

			private final WeakHashMap<A,B> cache = new WeakHashMap<A, B>();
			
			@Override
			public B apply(A a) {
				B result = cache.get(a);
				if (result != null) {
					return result;
				}
				result = f.apply(a);
				cache.put(a, result);
				return result;
			}
			
		};
	}
	
	public final <C> F<A,C> compose(final F<B,C> next) {
		return new F<A, C>() {
			@Override
			public C apply(A a) {
				return next.apply(F.this.apply(a));
			}
			
		};
	}	
}
